﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Jayden.Dll.Minecraft.Model;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace Sandbox
{
	public class SandboxGameWindow : GameWindow
	{

		private double angleInDegree = 180.0;

		Chunk chunk = new Chunk();

		public SandboxGameWindow()
			: base(800, 600, new GraphicsMode(new ColorFormat(8,8,8,8), 32), "Sandbox Game Window") { }

		List<int> textureIds = new List<int>();

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);


			VSync = VSyncMode.On;
			GL.ClearColor(Color.Purple);
			GL.Enable(EnableCap.DepthTest);
			GL.Enable(EnableCap.Texture2D);
			GL.Enable(EnableCap.Blend);
			GL.Enable(EnableCap.CullFace);
			GL.Enable(EnableCap.AlphaTest);

			GL.LineWidth(5);
			GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
			GL.ShadeModel(ShadingModel.Smooth);
			GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);

			string[] textureFiles = new string[] { @"C:\Users\Jonathan\Dropbox\earth.bmp", @"C:\Users\Jonathan\Dropbox\grass.png", @"C:\Users\Jonathan\Dropbox\moon.bmp", @"C:\Users\Jonathan\Desktop\Nouveau dossier\terrain.png"};

			foreach (string textureFile in textureFiles)
			{
				if (File.Exists(textureFile))
				{
					int textureId;
					Bitmap textureBitmap = new Bitmap(textureFile);
					BitmapData textureData = textureBitmap.LockBits(new Rectangle(0, 0, textureBitmap.Width, textureBitmap.Height), ImageLockMode.ReadOnly, textureBitmap.PixelFormat);

					GL.GenTextures(1, out textureId);
					textureIds.Add(textureId);
					GL.BindTexture(TextureTarget.Texture2D, textureId);

					OpenTK.Graphics.OpenGL.PixelFormat format = OpenTK.Graphics.OpenGL.PixelFormat.Bgr;
					PixelInternalFormat internalFormat = PixelInternalFormat.Rgb;
					if (textureBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
					{
						format = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
						internalFormat = PixelInternalFormat.Rgba;
					}

					GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, textureData.Width, textureData.Height, 0, format, PixelType.UnsignedByte, textureData.Scan0);
					textureBitmap.UnlockBits(textureData);

					GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)TextureMinFilter.Nearest);
					GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)TextureMinFilter.Nearest);
				}
			}


		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);


			GL.Viewport(ClientRectangle);

			Matrix4d projection = Matrix4d.CreatePerspectiveFieldOfView(Math.PI / 4.0, ClientRectangle.Width / (double)ClientRectangle.Height, 0.01, 256);

			GL.MatrixMode(MatrixMode.Projection);
			GL.LoadMatrix(ref projection);
		}

		private Vector3 m_Position = new Vector3(-2.0f, 8f, -2.0f);
		private double angleV = 90;
		private double angleH = 0;
		private bool angleChange = false;
		private bool clickChange = false;
		private int previousX = 0;
		private int previousY = 0;
		private Vector3 m_Target = new Vector3(-2.0f, 8f, -1.0f);
		private Vector3 m_Direction = Vector3.UnitZ;
		private Vector3 momentum = new Vector3(0.0f, 0.0f, 0.0f);

		private float playerEyesHeight = 1.62f;
		private float playerHeight = 1.8f;
		private float playerWidth = 0.6f;

		private bool checkHitbox = true;
		private bool fallingEnabled = true;

		private void RefreshTarget()
		{
			float dx = (float)(Math.Sin(angleH / 180 * Math.PI) * Math.Sin(angleV / 180 * Math.PI));
			float dy = (float)Math.Cos(angleV / 180 * Math.PI);
			float dz = (float)(Math.Cos(angleH / 180 * Math.PI) * Math.Sin(angleV / 180 * Math.PI));
			m_Direction = new Vector3(dx, dy, dz);
			m_Target = new Vector3(m_Position.X + dx, m_Position.Y + dy, m_Position.Z + dz);
		}

		protected bool IsValidForHitBox(int chunkX, int chunkY, int chunkZ, Vector3d? feetPosition)
		{
			if (checkHitbox)
			{
				int blockId = chunk[chunkX, chunkY + 1, chunkZ];
				int bottomBlockId = chunk[chunkX, chunkY, chunkZ];
				if(blockId != BlockManager.Air.Id || bottomBlockId != BlockManager.Air.Id)
					return false;

				if (feetPosition.HasValue)
				{
					int leftChunkX = (int)Math.Round(feetPosition.Value.X + playerWidth / 2.0);
					int rightChunkX = (int)Math.Round(feetPosition.Value.X - playerWidth / 2.0);
					int frontChunkZ = (int)Math.Round(feetPosition.Value.Z + playerWidth / 2.0);
					int backChunkZ = (int)Math.Round(feetPosition.Value.Z - playerWidth / 2.0);

					if (leftChunkX != chunkX && !IsValidForHitBox(leftChunkX, chunkY, chunkZ, null))
						return false;
					if (rightChunkX != chunkX && !IsValidForHitBox(rightChunkX, chunkY, chunkZ, null))
						return false;
					if (frontChunkZ != chunkZ && !IsValidForHitBox(chunkX, chunkY, frontChunkZ, null))
						return false;
					if (backChunkZ != chunkZ && !IsValidForHitBox(chunkX, chunkY, backChunkZ, null))
						return false;
				}
			}
			return true;
		}
		
		protected override void OnUpdateFrame(FrameEventArgs e)
		{
			base.OnUpdateFrame(e);

			Vector3d playerFeet = new Vector3d(m_Position.X, m_Position.Y - 1.62, m_Position.Z);
			int chunkX = (int)Math.Round(playerFeet.X);
			int chunkY = (int)Math.Round(playerFeet.Y);
			int chunkZ = (int)Math.Round(playerFeet.Z);

			if (fallingEnabled)
			{
				int blockId = chunk[chunkX, chunkY, chunkZ];
				int bottomBlockId = chunk[chunkX, chunkY - 1, chunkZ];

				if (blockId == BlockManager.Air.Id && bottomBlockId == BlockManager.Air.Id)
				{
					if (m_Position.Y > playerEyesHeight - 0.5f)
					{
						m_Position.Y -= 0.1f;
						m_Target.Y -= 0.1f;
					}
					else if (m_Position.Y < playerEyesHeight - 0.5f)
					{
						m_Position.Y = 1.62f - 0.5f;
						RefreshTarget();
					}
				}
				else if(blockId == BlockManager.Air.Id)
				{
					if (m_Position.Y > chunkY - 0.5f + playerEyesHeight)
					{
						m_Position.Y -= 0.1f;
						m_Target.Y -= 0.1f;
					}
					if (m_Position.Y < chunkY - 0.5f + playerEyesHeight)
					{
						m_Position.Y = chunkY - 0.5f + playerEyesHeight;
						RefreshTarget();
					}
				}
			}

			if (Keyboard[OpenTK.Input.Key.Escape])
				Close();

			float xMove = (float)Math.Sin(angleH / 180 * Math.PI);
			float zMove = (float)Math.Cos(angleH / 180 * Math.PI);

			Vector3 previousPosition = new Vector3(m_Position);
			Vector3 previousTarget = new Vector3(m_Target);

			if (Keyboard[OpenTK.Input.Key.Up] || Keyboard[OpenTK.Input.Key.Z])
			{
				m_Position.Z += 0.1f * zMove;
				m_Target.Z += 0.1f * zMove;
				m_Position.X += 0.1f * xMove;
				m_Target.X += 0.1f * xMove;
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}
			else if (Keyboard[OpenTK.Input.Key.Down] || Keyboard[OpenTK.Input.Key.S])
			{
				m_Position.Z -= 0.1f * zMove;
				m_Target.Z -= 0.1f * zMove;
				m_Position.X -= 0.1f * xMove;
				m_Target.X -= 0.1f * xMove;
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}
			if (Keyboard[OpenTK.Input.Key.Left] || Keyboard[OpenTK.Input.Key.Q])
			{
				m_Position.X += 0.1f * zMove;
				m_Target.X += 0.1f * zMove;
				m_Position.Z -= 0.1f * xMove;
				m_Target.Z -= 0.1f * xMove;
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}
			else if (Keyboard[OpenTK.Input.Key.Right] || Keyboard[OpenTK.Input.Key.D])
			{
				m_Position.X -= 0.1f * zMove;
				m_Target.X -= 0.1f * zMove;
				m_Position.Z += 0.1f * xMove;
				m_Target.Z += 0.1f * xMove;
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}
			if (Keyboard[OpenTK.Input.Key.Minus] || Keyboard[OpenTK.Input.Key.AltLeft])
			{
				m_Position.Y -= 0.1f;
				m_Target.Y -= 0.1f;
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}
			else if (Keyboard[OpenTK.Input.Key.Plus] || Keyboard[OpenTK.Input.Key.Space])
			{
				m_Position.Y += 0.1f;
				m_Target.Y += 0.1f;
				if (fallingEnabled)
				{
					m_Position.Y += 0.1f;
					m_Target.Y += 0.1f;
				}
				if (!IsValidForHitBox((int)Math.Round(m_Position.X), (int)Math.Round(m_Position.Y - playerEyesHeight + 0.001f), (int)Math.Round(m_Position.Z), playerFeet))
				{
					m_Position = previousPosition;
					m_Target = previousTarget;
				}
			}

			if (Mouse[OpenTK.Input.MouseButton.Right])
			{
				if (angleChange)
				{
					int deltaX = previousX - Mouse.X;
					int deltaY = Mouse.Y - previousY;

					angleV += deltaY * 0.3;
					angleH += deltaX * 0.3;

					if (angleV < 0)
						angleV = 0;
					if (angleV > 180)
						angleV = 180;
					while (angleH <= -180)
						angleH += 360;
					while (angleH > 180)
						angleH -= 360;

					float dx = (float)(Math.Sin(angleH / 180 * Math.PI) * Math.Sin(angleV / 180 * Math.PI));
					float dy = (float)Math.Cos(angleV / 180 * Math.PI);
					float dz = (float)(Math.Cos(angleH / 180 * Math.PI) * Math.Sin(angleV / 180 * Math.PI));
					m_Direction = new Vector3(dx, dy, dz);
					m_Target = new Vector3(m_Position.X + dx, m_Position.Y + dy, m_Position.Z + dz);
				}
				angleChange = true;
				previousX = Mouse.X;
				previousY = Mouse.Y;
			}
			else
				angleChange = false;

			Selected = Picking(Mouse.X, Mouse.Y);

			if (Selected != -1)
			{
				int sX = Selected % 16;
				int sY = Selected / 256;
				int sZ = (Selected % 256) / 16;

				chunk.Selected = new Vector3(sX, sY, sZ);
			}
			else
				chunk.Selected = new Vector3(-1, -1, -1);

			if (Mouse[OpenTK.Input.MouseButton.Left] && chunk.Selected.X >= 0)
			{
				if(!clickChange)
					chunk[(int)chunk.Selected.X, (int)chunk.Selected.Y, (int)chunk.Selected.Z] = 0;
				clickChange = true;
			}
			else
			{
				clickChange = false;
			}


		}

		private void PickMatrix(double x, double y, double deltax, double deltay, int[] viewport)
		{
			if (deltax <= 0 || deltay <= 0)
			{
				return;
			}

			/* Translate and scale the picked region to the entire window */
			GL.Translate((viewport[2] - 2 * (x - viewport[0])) / deltax,
				(viewport[3] - 2 * (y - viewport[1])) / deltay, 0);
			GL.Scale(viewport[2] / deltax, viewport[3] / deltay, 1.0);
		}


		static int[] selectBuffer = new int[128 * 4];
		protected int Picking(int x, int y)
		{
			GL.SelectBuffer(128 * 4, selectBuffer);
			GL.InitNames();
			GL.RenderMode(RenderingMode.Select);
			GL.MatrixMode(MatrixMode.Projection);
			GL.LoadIdentity();
			int[] viewport = new int[4];
			GL.GetInteger(GetPName.Viewport, viewport);
			PickMatrix(x, ClientRectangle.Height - y, 5.0, 5.0, viewport);
			PlaceViewport();
			GL.Flush(); // ?
			GL.PushMatrix();
			Render();
			GL.PopMatrix();

			int found = GL.RenderMode(RenderingMode.Render);
			uint closest = uint.MaxValue;
			int selectedId = -1;

			for (int i = 0; i < found; i++)
			{
				uint distance = (uint)selectBuffer[i * 4 + 1];

				if (closest >= distance)
				{
					closest = distance;
					selectedId = (int)selectBuffer[i * 4 + 3];
				}
			}

			return selectedId;
		}

		int Selected = -1;

		public void PlaceViewport()
		{
			Matrix4d projection = Matrix4d.CreatePerspectiveFieldOfView(Math.PI / 4.0, ClientRectangle.Width / (double)ClientRectangle.Height, 0.01, 256);
			GL.MultMatrix(ref projection);
			Matrix4 modelView = Matrix4.LookAt(m_Position, m_Target, Vector3.UnitY);
			GL.MatrixMode(MatrixMode.Modelview);
			GL.LoadMatrix(ref modelView);
		}

		public void DefineLights()
		{
			GL.Light(LightName.Light0, LightParameter.Position, new Vector4(1, 1, 0, 0));
			GL.Light(LightName.Light0, LightParameter.Ambient, new Color4(0.6f, 0.6f, 0.6f, 1.0f));
			GL.Light(LightName.Light0, LightParameter.Diffuse, new Color4(1.0f, 1.0f, 1.0f, 1.0f));
			GL.Light(LightName.Light0, LightParameter.Specular, new Color4(1.0f, 1.0f, 1.0f, 1.0f));

			GL.Light(LightName.Light1, LightParameter.Position, new Vector4(-8, 8, -8, 1));
			GL.Light(LightName.Light1, LightParameter.Ambient, new Color4(0.6f, 0.6f, 0.6f, 1.0f));
			GL.Light(LightName.Light1, LightParameter.Diffuse, new Color4(1.0f, 1.0f, 1.0f, 1.0f));
			GL.Light(LightName.Light1, LightParameter.Specular, new Color4(1.0f, 1.0f, 1.0f, 1.0f));
		}

		public void DefineViewport()
		{
			GL.Viewport(ClientRectangle);
			GL.MatrixMode(MatrixMode.Projection);
			GL.LoadIdentity();
			PlaceViewport();
			DefineLights();
		}

		public void EnableLight()
		{
			GL.Enable(EnableCap.Lighting);
			GL.Enable(EnableCap.Light0);
			GL.Enable(EnableCap.Light1);
		}

		public void DisableLight()
		{
			GL.Disable(EnableCap.Lighting);
			GL.Disable(EnableCap.Light0);
			GL.Disable(EnableCap.Light1);
		}

		protected override void OnRenderFrame(FrameEventArgs e)
		{
			base.OnRenderFrame(e);

			GL.DepthMask(true);
			GL.ClearColor(1.0f, 0.0f, 1.0f, 0.0f);
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			EnableLight();
			DefineViewport();
			GL.PushMatrix();
			Render();
			GL.PopMatrix();

			SwapBuffers();
		}

		protected void Render()
		{
			GL.Material(MaterialFace.Front, MaterialParameter.Emission, new Color4(0.0f, 0.0f, 0.0f, 1.0f));
			GL.Material(MaterialFace.Front, MaterialParameter.Specular, new Color4(1.0f, 1.0f, 1.0f, 1.0f));
			GL.Material(MaterialFace.Front, MaterialParameter.Shininess, 50);

			GL.BindTexture(TextureTarget.Texture2D, textureIds[3]);
			GL.DepthMask(true);
			GL.Disable(EnableCap.Blend);
			GL.AlphaFunc(AlphaFunction.Equal, 1.0f);
			chunk.Render(0);
			GL.Enable(EnableCap.Blend);
			GL.DepthMask(false);
			GL.AlphaFunc(AlphaFunction.Less, 1.0f);
			chunk.Render(0);
		}

	}
}
